/* Copyright 2018 Canaan Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * File: printf.c
 * 
 * Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Kustaa Nyholm or SpareTimeLabs nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <printf.h>
#include <stddef.h>
#include "FreeRTOS.h"
//#include <task.h>
#include "semphr.h"

/*
 * Configuration
 */

/* Enable long int support */
#define PRINTF_LONG_SUPPORT

/* Enable long long int support (implies long int support) */
#define PRINTF_LONG_LONG_SUPPORT

/* Enable %z (size_t) support */
#define PRINTF_SIZE_T_SUPPORT

/*
 * Configuration adjustments
 */
#ifdef PRINTF_LONG_LONG_SUPPORT
#define PRINTF_LONG_SUPPORT
#endif

/* __SIZEOF_<type>__ defined at least by gcc */
#ifdef __SIZEOF_POINTER__
#define SIZEOF_POINTER __SIZEOF_POINTER__
#endif
#ifdef __SIZEOF_LONG_LONG__
#define SIZEOF_LONG_LONG __SIZEOF_LONG_LONG__
#endif
#ifdef __SIZEOF_LONG__
#define SIZEOF_LONG __SIZEOF_LONG__
#endif
#ifdef __SIZEOF_INT__
#define SIZEOF_INT __SIZEOF_INT__
#endif

#ifdef __GNUC__
#define _TFP_GCC_NO_INLINE_ __attribute__((noinline))
#else
#define _TFP_GCC_NO_INLINE_
#endif

#define IS_DIGIT(x) ((x) >= '0' && (x) <= '9')

#ifdef PRINTF_LONG_SUPPORT
#define BF_MAX 20 /* long = 64b on some architectures */
#else
#define BF_MAX 10 /* int = 32b on some architectures */
#endif


/*
 * LoBo:
 *   make syslog level configurable during run time
 *   add syslog mutex
 *   enable filtering of non-printable characters
 */
//--------------------------------
uint32_t        user_log_level = 2;
uint32_t        user_log_color = 1;
QueueHandle_t   syslog_mutex = NULL;
int             kprint_filter_nonprint = 1;
char            kprint_nonprint_char = '~';
uint8_t         kprint_cr_lf = 1;
//--------------------------------

/*
 * Implementation
 */
struct param
{
    char lz : 1; /**<  Leading zeros */
    char alt : 1; /**<  alternate form */
    char uc : 1; /**<  Upper case (for base16 only) */
    char align_left : 1; /**<  0 == align right (default), 1 == align left
				*/
    int width; /**<  field width */
    int prec; /**<  precision */
    char sign; /**<  The sign to display (if any) */
    unsigned int base; /**<  number base (e.g.: 8, 10, 16) */
    char* bf; /**<  Buffer to output */
    size_t bf_len; /**<  Buffer length */
};

#ifdef PRINTF_LONG_LONG_SUPPORT
static void _TFP_GCC_NO_INLINE_ ulli2a(unsigned long long int num, struct param* p)
{
    unsigned long long int d = 1;
    char* bf = p->bf;
    if ((p->prec == 0) && (num == 0))
        return;
    while (num / d >= p->base)
    {
        d *= p->base;
    }
    while (d != 0)
    {
        int dgt = num / d;
        num %= d;
        d /= p->base;
        *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
    }
    p->bf_len = bf - p->bf;
}

static void lli2a(long long int num, struct param* p)
{
    if (num < 0)
    {
        num = -num;
        p->sign = '-';
    }
    ulli2a(num, p);
}
#endif

#ifdef PRINTF_LONG_SUPPORT
static void uli2a(unsigned long int num, struct param* p)
{
    unsigned long int d = 1;
    char* bf = p->bf;
    if ((p->prec == 0) && (num == 0))
        return;
    while (num / d >= p->base)
    {
        d *= p->base;
    }
    while (d != 0)
    {
        int dgt = num / d;
        num %= d;
        d /= p->base;
        *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
    }
    p->bf_len = bf - p->bf;
}

static void li2a(long num, struct param* p)
{
    if (num < 0)
    {
        num = -num;
        p->sign = '-';
    }
    uli2a(num, p);
}
#endif

static void ui2a(unsigned int num, struct param* p)
{
    unsigned int d = 1;
    char* bf = p->bf;
    if ((p->prec == 0) && (num == 0))
        return;
    while (num / d >= p->base)
    {
        d *= p->base;
    }
    while (d != 0)
    {
        int dgt = num / d;
        num %= d;
        d /= p->base;
        *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
    }
    p->bf_len = bf - p->bf;
}

static void i2a(int num, struct param* p)
{
    if (num < 0)
    {
        num = -num;
        p->sign = '-';
    }
    ui2a(num, p);
}

static int a2d(char ch)
{
    if (IS_DIGIT(ch))
        return ch - '0';
    else if (ch >= 'a' && ch <= 'f')
        return ch - 'a' + 10;
    else if (ch >= 'A' && ch <= 'F')
        return ch - 'A' + 10;
    else
        return -1;
}

static char a2u(char ch, const char** src, int base, unsigned int* nump)
{
    const char* p = *src;
    unsigned int num = 0;
    int digit;
    while ((digit = a2d(ch)) >= 0)
    {
        if (digit > base)
            break;
        num = num * base + digit;
        ch = *p++;
    }
    *src = p;
    *nump = num;
    return ch;
}

static void putchw(void* putp, putcf putf, struct param* p)
{
    char ch;
    int width = p->width;
    int prec = p->prec;
    char* bf = p->bf;
    size_t bf_len = p->bf_len;

    /* Number of filling characters */
    width -= bf_len;
    prec -= bf_len;
    if (p->sign)
        width--;
    if (p->alt && p->base == 16)
        width -= 2;
    else if (p->alt && p->base == 8)
        width--;
    if (prec > 0)
        width -= prec;

    /* Fill with space to align to the right, before alternate or sign */
    if (!p->lz && !p->align_left)
    {
        while (width-- > 0)
            putf(putp, ' ');
    }

    /* print sign */
    if (p->sign)
        putf(putp, p->sign);

    /* Alternate */
    if (p->alt && p->base == 16)
    {
        putf(putp, '0');
        putf(putp, (p->uc ? 'X' : 'x'));
    }
    else if (p->alt && p->base == 8)
    {
        putf(putp, '0');
    }

    /* Fill with zeros, after alternate or sign */
    while (prec-- > 0)
        putf(putp, '0');
    if (p->lz)
    {
        while (width-- > 0)
            putf(putp, '0');
    }

    /* Put actual buffer */
    while ((bf_len-- > 0) && (ch = *bf++))
    {
        // LoBo: filter non-printable characters
        if ((!kprint_filter_nonprint) || (((ch >= 0x20) && (ch < 0x80)))) putf(putp, ch);
        else if ((kprint_cr_lf) && (ch == '\r')) {
            putf(putp, '\\');
            putf(putp, 'r');
        }
        else if ((kprint_cr_lf) && (ch == '\n')) {
            putf(putp, '\\');
            putf(putp, 'n');
        }
        else putf(putp, kprint_nonprint_char);
    }

    /* Fill with space to align to the left, after string */
    if (!p->lz && p->align_left)
    {
        while (width-- > 0)
            putf(putp, ' ');
    }
}

void tfp_format(void* putp, putcf putf, const char* fmt, va_list va)
{
    struct param p;
    char bf[BF_MAX];
    char ch;

    while ((ch = *(fmt++)))
    {
        if (ch != '%')
        {
            putf(putp, ch);
        }
        else
        {
#ifdef PRINTF_LONG_SUPPORT
            char lng = 0; /* 1 for long, 2 for long long */
#endif
            /* Init parameter struct */
            p.lz = 0;
            p.alt = 0;
            p.uc = 0;
            p.align_left = 0;
            p.width = 0;
            p.prec = -1;
            p.sign = 0;
            p.bf = bf;
            p.bf_len = 0;

            /* Flags */
            while ((ch = *(fmt++)))
            {
                switch (ch)
                {
                case '-':
                    p.align_left = 1;
                    continue;
                case '0':
                    p.lz = 1;
                    continue;
                case '#':
                    p.alt = 1;
                    continue;
                default:
                    break;
                }
                break;
            }

            if (p.align_left)
                p.lz = 0;

            /* Width */
            if (ch == '*')
            {
                ch = *(fmt++);
                p.width = va_arg(va, int);
                if (p.width < 0)
                {
                    p.align_left = 1;
                    p.width = -p.width;
                }
            }
            else if (IS_DIGIT(ch))
            {
                unsigned int width;
                ch = a2u(ch, &fmt, 10, &(width));
                p.width = width;
            }

            /* Precision */
            if (ch == '.')
            {
                ch = *(fmt++);
                if (ch == '*')
                {
                    int prec;
                    ch = *(fmt++);
                    prec = va_arg(va, int);
                    if (prec < 0)
                        /* act as if precision was
						 * omitted */
                        p.prec = -1;
                    else
                        p.prec = prec;
                }
                else if (IS_DIGIT(ch))
                {
                    unsigned int prec;
                    ch = a2u(ch, &fmt, 10, &(prec));
                    p.prec = prec;
                }
                else
                {
                    p.prec = 0;
                }
            }
            if (p.prec >= 0)
                /* precision causes zero pad to be ignored */
                p.lz = 0;

#ifdef PRINTF_SIZE_T_SUPPORT
#ifdef PRINTF_LONG_SUPPORT
            if (ch == 'z')
            {
                ch = *(fmt++);
                if (sizeof(size_t) == sizeof(unsigned long int))
                    lng = 1;
#ifdef PRINTF_LONG_LONG_SUPPORT
                else if (sizeof(size_t) == sizeof(unsigned long long int))
                    lng = 2;
#endif
            }
            else
#endif
#endif

#ifdef PRINTF_LONG_SUPPORT
                if (ch == 'l')
            {
                ch = *(fmt++);
                lng = 1;
#ifdef PRINTF_LONG_LONG_SUPPORT
                if (ch == 'l')
                {
                    ch = *(fmt++);
                    lng = 2;
                }
#endif
            }
#endif
            switch (ch)
            {
            case 0:
                goto abort;
            case 'u':
                p.base = 10;
                if (p.prec < 0)
                    p.prec = 1;
#ifdef PRINTF_LONG_SUPPORT
#ifdef PRINTF_LONG_LONG_SUPPORT
                if (2 == lng)
                    ulli2a(va_arg(va, unsigned long long int), &p);
                else
#endif
                    if (1 == lng)
                    uli2a(va_arg(va, unsigned long int), &p);
                else
#endif
                    ui2a(va_arg(va, unsigned int), &p);
                putchw(putp, putf, &p);
                break;
            case 'd':
            case 'i':
                p.base = 10;
                if (p.prec < 0)
                    p.prec = 1;
#ifdef PRINTF_LONG_SUPPORT
#ifdef PRINTF_LONG_LONG_SUPPORT
                if (2 == lng)
                    lli2a(va_arg(va, long long int), &p);
                else
#endif
                    if (1 == lng)
                    li2a(va_arg(va, long int), &p);
                else
#endif
                    i2a(va_arg(va, int), &p);
                putchw(putp, putf, &p);
                break;
#ifdef SIZEOF_POINTER
            case 'p':
                p.alt = 1;
#if defined(SIZEOF_INT) && SIZEOF_POINTER <= SIZEOF_INT
                lng = 0;
#elif defined(SIZEOF_LONG) && SIZEOF_POINTER <= SIZEOF_LONG
                lng = 1;
#elif defined(SIZEOF_LONG_LONG) && SIZEOF_POINTER <= SIZEOF_LONG_LONG
                lng = 2;
#endif
#endif
            case 'x':
            case 'X':
                p.base = 16;
                p.uc = (ch == 'X') ? 1 : 0;
                if (p.prec < 0)
                    p.prec = 1;
#ifdef PRINTF_LONG_SUPPORT
#ifdef PRINTF_LONG_LONG_SUPPORT
                if (2 == lng)
                    ulli2a(va_arg(va, unsigned long long int), &p);
                else
#endif
                    if (1 == lng)
                    uli2a(va_arg(va, unsigned long int), &p);
                else
#endif
                    ui2a(va_arg(va, unsigned int), &p);
                putchw(putp, putf, &p);
                break;
            case 'o':
                p.base = 8;
                if (p.prec < 0)
                    p.prec = 1;
                ui2a(va_arg(va, unsigned int), &p);
                putchw(putp, putf, &p);
                break;
            case 'c':
                putf(putp, (char)(va_arg(va, int)));
                break;
            case 's':
            {
                unsigned int prec = p.prec;
                char* b;
                p.bf = va_arg(va, char*);
                b = p.bf;
                while ((prec-- != 0) && *b++)
                {
                    p.bf_len++;
                }
                p.prec = -1;
                putchw(putp, putf, &p);
            }
            break;
            case '%':
                putf(putp, ch);
            default:
                break;
            }
        }
    }
abort:;
}

#if TINYPRINTF_DEFINE_TFP_PRINTF
static putcf stdout_putf;
static void* stdout_putp;

void init_printf(void* putp, putcf putf)
{
    stdout_putf = putf;
    stdout_putp = putp;
}

void tfp_printf(char* fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    tfp_format(stdout_putp, stdout_putf, fmt, va);
    va_end(va);
}
#endif

#if TINYPRINTF_DEFINE_TFP_SPRINTF
struct _vsnprintf_putcf_data
{
    size_t dest_capacity;
    char* dest;
    size_t num_chars;
};

static void _vsnprintf_putcf(void* p, char c)
{
    struct _vsnprintf_putcf_data* data = (struct _vsnprintf_putcf_data*)p;
    if (data->num_chars < data->dest_capacity)
        data->dest[data->num_chars] = c;
    data->num_chars++;
}

int tfp_vsnprintf(char* str, size_t size, const char* format, va_list ap)
{
    struct _vsnprintf_putcf_data data;

    if (size < 1)
        return 0;

    data.dest = str;
    data.dest_capacity = size - 1;
    data.num_chars = 0;
    tfp_format(&data, _vsnprintf_putcf, format, ap);

    if (data.num_chars < data.dest_capacity)
        data.dest[data.num_chars] = '\0';
    else
        data.dest[data.dest_capacity] = '\0';

    return data.num_chars;
}

int tfp_snprintf(char* str, size_t size, const char* format, ...)
{
    va_list ap;
    int retval;

    va_start(ap, format);
    retval = tfp_vsnprintf(str, size, format, ap);
    va_end(ap);
    return retval;
}

struct _vsprintf_putcf_data
{
    char* dest;
    size_t num_chars;
};

static void _vsprintf_putcf(void* p, char c)
{
    struct _vsprintf_putcf_data* data = (struct _vsprintf_putcf_data*)p;
    data->dest[data->num_chars++] = c;
}

int tfp_vsprintf(char* str, const char* format, va_list ap)
{
    struct _vsprintf_putcf_data data;
    data.dest = str;
    data.num_chars = 0;
    tfp_format(&data, _vsprintf_putcf, format, ap);
    data.dest[data.num_chars] = '\0';
    return data.num_chars;
}

int tfp_sprintf(char* str, const char* format, ...)
{
    va_list ap;
    int retval;

    va_start(ap, format);
    retval = tfp_vsprintf(str, format, ap);
    va_end(ap);
    return retval;
}
#endif

/* Clear unused warnings for actually unused variables */
#define UNUSED(x) (void)(x)

#include "atomic.h"
#include "uarths.h"

static corelock_t lock = CORELOCK_INIT;

static void uart_putf(void* unused, char c)
{
    UNUSED(unused);
    uarths_write_byte(c);
}

int printk(const char* format, ...)
{
    if (syslog_mutex) {
        xSemaphoreTake(syslog_mutex, 100 / portTICK_RATE_MS);
    }
    va_list ap;

    va_start(ap, format);
    /* Begin protected code */
    corelock_lock(&lock);
    tfp_format(stdout_putp, uart_putf, format, ap);
    /* End protected code */
    corelock_unlock(&lock);
    va_end(ap);

    if (syslog_mutex) {
        xSemaphoreGive(syslog_mutex);
    }
    return 0;
}
